package com.beanu.arad.widget;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.KeyEventCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.Scroller;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;


/**
 * 不缓存的ViewPager
 * <p>
 * Created by Shuwen on 2016年7月9日10:03:57
 */

public class NoCacheViewPager extends NoScrollViewPager {
    private static final String TAG = "LazyViewPager";
    private static final boolean DEBUG = false;
    private static final boolean USE_CACHE = false;
    private static final int DEFAULT_OFFSCREEN_PAGES = 0;// 默认的加载页面,ViewPager是1个,所以会加载两个Fragment
    private static final int MAX_SETTLE_DURATION = 600; // ms

    static class ItemInfo {
        Object object;
        int position;
        boolean scrolling;
    }

    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {

        @Override

        public int compare(ItemInfo lhs, ItemInfo rhs) {
            return lhs.position - rhs.position;
        }

    };


    private static final Interpolator sInterpolator = new Interpolator() {

        public float getInterpolation(float t) {
            // _o(t) = t * t * ((tension + 1) * t + tension)
            // o(t) = _o(t - 1) + 1
            t -= 1.0f;
            return t * t * t + 1.0f;
        }

    };

    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
    private PagerAdapter mAdapter;
    private int mCurItem; // Index of currently displayed page.
    private int mRestoredCurItem = -1;
    private Parcelable mRestoredAdapterState = null;
    private ClassLoader mRestoredClassLoader = null;
    private Scroller mScroller;
    private PagerObserver mObserver;

    private int mPageMargin;
    private Drawable mMarginDrawable;
    private int mChildWidthMeasureSpec;
    private int mChildHeightMeasureSpec;
    private boolean mInLayout;
    private boolean mScrollingCacheEnabled;
    private boolean mPopulatePending;
    private boolean mScrolling;
    private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
    private boolean mIsBeingDragged;
    private boolean mIsUnableToDrag;
    private int mTouchSlop;
    private float mInitialMotionX;

    /**
     * Position of the last motion event.
     */
    private float mLastMotionX;
    private float mLastMotionY;

    /**
     * ID of the active pointer. This is used to retain consistency during
     * <p>
     * drags/flings if multiple pointers are used.
     */
    private int mActivePointerId = INVALID_POINTER;

    /**
     * Sentinel value for no current active pointer. Used by
     * <p>
     * {@link #mActivePointerId}.
     */
    private static final int INVALID_POINTER = -1;

    /**
     * Determines speed during touch scrolling
     */
    private VelocityTracker mVelocityTracker;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private float mBaseLineFlingVelocity;
    private float mFlingVelocityInfluence;
    private boolean mFakeDragging;
    private long mFakeDragBeginTime;
    private EdgeEffectCompat mLeftEdge;
    private EdgeEffectCompat mRightEdge;
    private boolean mFirstLayout = true;
    private OnPageChangeListener mOnPageChangeListener;

    /**
     * Indicates that the pager is in an idle, settled state. The current page
     * <p>
     * is fully in view and no animation is in progress.
     */
    public static final int SCROLL_STATE_IDLE = 0;

    /**
     * Indicates that the pager is currently being dragged by the user.
     */
    public static final int SCROLL_STATE_DRAGGING = 1;

    /**
     * Indicates that the pager is in the process of settling to a final
     * <p>
     * position.
     */
    public static final int SCROLL_STATE_SETTLING = 2;
    private int mScrollState = SCROLL_STATE_IDLE;

    /**
     * Callback interface for responding to changing state of the selected page.
     */
    public interface OnPageChangeListener {

        /**
         * This method will be invoked when the current page is scrolled, either
         * <p>
         * as part of a programmatically initiated smooth scroll or a user
         * <p>
         * initiated touch scroll.
         *
         * @param position             Position index of the first page currently being
         *                             <p>
         *                             displayed. Page position+1 will be visible if
         *                             <p>
         *                             positionOffset is nonzero.
         * @param positionOffset       Value from [0, 1) indicating the offset from the page at
         *                             <p>
         *                             position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        public void onPageScrolled(int position, float positionOffset,
                                   int positionOffsetPixels);


        /**
         * This method will be invoked when a new page becomes selected.
         * <p>
         * Animation is not necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        public void onPageSelected(int position);


        /**
         * Called when the scroll state changes. Useful for discovering when the
         * <p>
         * user begins dragging, when the pager is automatically settling to the
         * <p>
         * current page, or when it is fully stopped/idle.
         *
         * @param state The new scroll state.
         * @see android.support.v4.view.ViewPager#SCROLL_STATE_IDLE
         * @see android.support.v4.view.ViewPager#SCROLL_STATE_DRAGGING
         * @see android.support.v4.view.ViewPager#SCROLL_STATE_SETTLING
         */
        public void onPageScrollStateChanged(int state);

    }


    /**
     * Simple implementation of the
     * <p>
     * interface with stub implementations of each method. Extend this if you do
     * <p>
     * not intend to override every method of
     */
    public static class SimpleOnPageChangeListener implements
            OnPageChangeListener {
        @Override
        public void onPageScrolled(int position, float positionOffset,
                                   int positionOffsetPixels) {
            // This space for rent
        }

        @Override
        public void onPageSelected(int position) {
            // This space for rent
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            // This space for rent
        }

    }


    public NoCacheViewPager(Context context) {
        super(context);
        initViewPager();
    }


    public NoCacheViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViewPager();
    }


    void initViewPager() {
        setWillNotDraw(false);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setFocusable(true);
        final Context context = getContext();
        mScroller = new Scroller(context, sInterpolator);
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat
                .getScaledPagingTouchSlop(configuration);
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mLeftEdge = new EdgeEffectCompat(context);
        mRightEdge = new EdgeEffectCompat(context);

        float density = context.getResources().getDisplayMetrics().density;
        mBaseLineFlingVelocity = 2500.0f * density;
        mFlingVelocityInfluence = 0.4f;
    }


    private void setScrollState(int newState) {
        if (mScrollState == newState) {
            return;
        }

        mScrollState = newState;
        if (mOnPageChangeListener != null) {
            mOnPageChangeListener.onPageScrollStateChanged(newState);
        }

    }

    public void setAdapter(PagerAdapter adapter) {
        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mObserver);
            mAdapter.startUpdate(this);
            for (int i = 0; i < mItems.size(); i++) {
                final ItemInfo ii = mItems.get(i);
                mAdapter.destroyItem(this, ii.position, ii.object);
            }

            mAdapter.finishUpdate(this);
            mItems.clear();
            removeAllViews();
            mCurItem = 0;
            scrollTo(0, 0);

        }

        mAdapter = adapter;
        if (mAdapter != null) {
            if (mObserver == null) {
                mObserver = new PagerObserver();
            }
            mAdapter.registerDataSetObserver(mObserver);
            mPopulatePending = false;
            if (mRestoredCurItem >= 0) {
                mAdapter.restoreState(mRestoredAdapterState,
                        mRestoredClassLoader);
                setCurrentItemInternal(mRestoredCurItem, false, true);
                mRestoredCurItem = -1;
                mRestoredAdapterState = null;
                mRestoredClassLoader = null;
            } else {
                populate();
            }

        }

    }


    public PagerAdapter getAdapter() {

        return mAdapter;
    }


    /**
     * Set the currently selected page. If the ViewPager has already been
     * <p>
     * through its first layout there will be a smooth animated transition
     * <p>
     * between the current item and the specified item.
     *
     * @param item Item index to select
     */
    public void setCurrentItem(int item) {
        mPopulatePending = false;
        setCurrentItemInternal(item, !mFirstLayout, false);
    }

    /**
     * Set the currently selected page.
     *
     * @param item         Item index to select
     * @param smoothScroll True to smoothly scroll to the new item, false to transition
     *                     <p>
     *                     immediately
     */
    public void setCurrentItem(int item, boolean smoothScroll) {
        mPopulatePending = false;
        setCurrentItemInternal(item, smoothScroll, false);

    }


    public int getCurrentItem() {
        return mCurItem;
    }

    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
        setCurrentItemInternal(item, smoothScroll, always, 0);
    }

    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always,
                                int velocity) {
        if (mAdapter == null || mAdapter.getCount() <= 0) {
            setScrollingCacheEnabled(false);
            return;
        }
        if (!always && mCurItem == item && mItems.size() != 0) {
            setScrollingCacheEnabled(false);
            return;
        }

        if (item < 0) {
            item = 0;
        } else if (item >= mAdapter.getCount()) {
            item = mAdapter.getCount() - 1;
        }

        final int pageLimit = mOffscreenPageLimit;
        if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
            // We are doing a jump by more than one page. To avoid
            // glitches, we want to keep all current pages in the view
            // until the scroll ends.
            for (int i = 0; i < mItems.size(); i++) {
                mItems.get(i).scrolling = true;
            }
        }

        final boolean dispatchSelected = mCurItem != item;
        mCurItem = item;
        populate();
        final int destX = (getWidth() + mPageMargin) * item;
        if (smoothScroll) {
            smoothScrollTo(destX, 0, velocity);
            if (dispatchSelected && mOnPageChangeListener != null) {
                mOnPageChangeListener.onPageSelected(item);
            }
        } else {
            if (dispatchSelected && mOnPageChangeListener != null) {
                mOnPageChangeListener.onPageSelected(item);
            }
            completeScroll();
            scrollTo(destX, 0);
        }

    }

    public void setOnPageChangeListener(OnPageChangeListener listener) {
        mOnPageChangeListener = listener;
    }

    /**
     * Returns the number of pages that will be retained to either side of the
     * <p>
     * current page in the view hierarchy in an idle state. Defaults to 1.
     *
     * @return How many pages will be kept offscreen on either side
     * @see #setOffscreenPageLimit(int)
     */
    public int getOffscreenPageLimit() {
        return mOffscreenPageLimit;
    }

    /**
     * Set the number of pages that should be retained to either side of the
     * <p>
     * current page in the view hierarchy in an idle state. Pages beyond this
     * <p>
     * limit will be recreated from the adapter when needed.
     * <p>
     * <p>
     * <p>
     * <p>
     * <p>
     * This is offered as an optimization. If you know in advance the number of
     * <p>
     * pages you will need to support or have lazy-loading mechanisms in place
     * <p>
     * on your pages, tweaking this setting can have benefits in perceived
     * <p>
     * smoothness of paging animations and interaction. If you have a small
     * <p>
     * number of pages (3-4) that you can keep active all at once, less time
     * <p>
     * will be spent in layout for newly created view subtrees as the user pages
     * <p>
     * back and forth.
     * <p>
     * </p>
     * <p>
     * <p>
     * <p>
     * <p>
     * <p>
     * You should keep this limit low, especially if your pages have complex
     * <p>
     * layouts. This setting defaults to 1.
     * <p>
     * </p>
     *
     * @param limit How many pages will be kept offscreen in an idle state.
     */
    public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit
                    + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }

        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }

    }


    /**
     * Set the margin between pages.
     *
     * @param marginPixels Distance between adjacent pages in pixels
     * @see #getPageMargin()
     * @see #setPageMarginDrawable(Drawable)
     * @see #setPageMarginDrawable(int)
     */
    public void setPageMargin(int marginPixels) {
        final int oldMargin = mPageMargin;
        mPageMargin = marginPixels;
        final int width = getWidth();
        recomputeScrollPosition(width, width, marginPixels, oldMargin);
        requestLayout();
    }


    /**
     * Return the margin between pages.
     *
     * @return The size of the margin in pixels
     */
    public int getPageMargin() {
        return mPageMargin;
    }

    /**
     * Set a drawable that will be used to fill the margin between pages.
     *
     * @param d Drawable to display between pages
     */
    public void setPageMarginDrawable(Drawable d) {
        mMarginDrawable = d;
        if (d != null)
            refreshDrawableState();
        setWillNotDraw(d == null);
        invalidate();

    }


    /**
     * Set a drawable that will be used to fill the margin between pages.
     *
     * @param resId Resource ID of a drawable to display between pages
     */
    public void setPageMarginDrawable(int resId) {
        setPageMarginDrawable(getContext().getResources().getDrawable(resId));
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return super.verifyDrawable(who) || who == mMarginDrawable;
    }


    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        final Drawable d = mMarginDrawable;
        if (d != null && d.isStateful()) {
            d.setState(getDrawableState());
        }
    }


    // We want the duration of the page snap animation to be influenced by the
    // distance that
    // the screen has to travel, however, we don't want this duration to be
    // effected in a
    // purely linear fashion. Instead, we use this method to moderate the effect
    // that the distance
    // of travel has on the overall snap duration.
    float distanceInfluenceForSnapDuration(float f) {
        f -= 0.5f; // center the values about 0.
        f *= 0.3f * Math.PI / 2.0f;
        return (float) Math.sin(f);

    }


    /**
     * Like {@link View#scrollBy}, but scroll smoothly instead of
     * <p>
     * immediately.
     *
     * @param x the number of pixels to scroll by on the X axis
     * @param y the number of pixels to scroll by on the Y axis
     */
    void smoothScrollTo(int x, int y) {
        smoothScrollTo(x, y, 0);
    }

    /**
     * Like {@link View#scrollBy}, but scroll smoothly instead of
     * <p>
     * immediately.
     *
     * @param x        the number of pixels to scroll by on the X axis
     * @param y        the number of pixels to scroll by on the Y axis
     * @param velocity the velocity associated with a fling, if applicable. (0
     *                 <p>
     *                 otherwise)
     */
    void smoothScrollTo(int x, int y, int velocity) {
        if (getChildCount() == 0) {
            // Nothing to do.
            setScrollingCacheEnabled(false);
            return;
        }

        int sx = getScrollX();
        int sy = getScrollY();
        int dx = x - sx;
        int dy = y - sy;
        if (dx == 0 && dy == 0) {
            completeScroll();
            setScrollState(SCROLL_STATE_IDLE);
            return;
        }

        setScrollingCacheEnabled(true);
        mScrolling = true;
        setScrollState(SCROLL_STATE_SETTLING);
        final float pageDelta = (float) Math.abs(dx)
                / (getWidth() + mPageMargin);
        int duration = (int) (pageDelta * 100);
        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration += (duration / (velocity / mBaseLineFlingVelocity))
                    * mFlingVelocityInfluence;
        } else {
            duration += 100;
        }

        duration = Math.min(duration, MAX_SETTLE_DURATION);
        mScroller.startScroll(sx, sy, dx, dy, duration);
        invalidate();
    }


    void addNewItem(int position, int index) {

        ItemInfo ii = new ItemInfo();
        ii.position = position;
        ii.object = mAdapter.instantiateItem(this, position);
        if (index < 0) {
            mItems.add(ii);
        } else {
            mItems.add(index, ii);
        }

    }

    void dataSetChanged() {
        // This method only gets called if our observer is attached, so mAdapter
        // is non-null.
        boolean needPopulate = mItems.size() < 3
                && mItems.size() < mAdapter.getCount();
        int newCurrItem = -1;
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            final int newPos = mAdapter.getItemPosition(ii.object);
            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }

            if (newPos == PagerAdapter.POSITION_NONE) {
                mItems.remove(i);
                i--;
                mAdapter.destroyItem(this, ii.position, ii.object);
                needPopulate = true;
                if (mCurItem == ii.position) {
                    // Keep the current item in the valid range
                    newCurrItem = Math.max(0,
                            Math.min(mCurItem, mAdapter.getCount() - 1));
                }
                continue;
            }


            if (ii.position != newPos) {

                if (ii.position == mCurItem) {

                    // Our current item changed position. Follow it.

                    newCurrItem = newPos;

                }


                ii.position = newPos;

                needPopulate = true;

            }

        }


        Collections.sort(mItems, COMPARATOR);


        if (newCurrItem >= 0) {

            // TODO This currently causes a jump.

            setCurrentItemInternal(newCurrItem, false, true);

            needPopulate = true;

        }

        if (needPopulate) {

            populate();

            requestLayout();

        }

    }


    void populate() {

        if (mAdapter == null) {

            return;

        }


        // Bail now if we are waiting to populate. This is to hold off

        // on creating views from the time the user releases their finger to

        // fling to a new position until we have finished the scroll to

        // that position, avoiding glitches from happening at that point.

        if (mPopulatePending) {

            if (DEBUG)

                Log.i(TAG, "populate is pending, skipping for now...");

            return;

        }


        // Also, don't populate until we are attached to a window. This is to

        // avoid trying to populate before we have restored our view hierarchy

        // state and conflicting with what is restored.

        if (getWindowToken() == null) {

            return;

        }


        mAdapter.startUpdate(this);


        final int pageLimit = mOffscreenPageLimit;

        final int startPos = Math.max(0, mCurItem - pageLimit);

        final int N = mAdapter.getCount();

        final int endPos = Math.min(N - 1, mCurItem + pageLimit);


        if (DEBUG)

            Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);


        // Add and remove pages in the existing list.

        int lastPos = -1;

        for (int i = 0; i < mItems.size(); i++) {

            ItemInfo ii = mItems.get(i);

            if ((ii.position < startPos || ii.position > endPos)

                    && !ii.scrolling) {

                if (DEBUG)

                    Log.i(TAG, "removing: " + ii.position + " @ " + i);

                mItems.remove(i);

                i--;

                mAdapter.destroyItem(this, ii.position, ii.object);

            } else if (lastPos < endPos && ii.position > startPos) {

                // The next item is outside of our range, but we have a gap

                // between it and the last item where we want to have a page

                // shown. Fill in the gap.

                lastPos++;

                if (lastPos < startPos) {

                    lastPos = startPos;

                }

                while (lastPos <= endPos && lastPos < ii.position) {

                    if (DEBUG)

                        Log.i(TAG, "inserting: " + lastPos + " @ " + i);

                    addNewItem(lastPos, i);

                    lastPos++;

                    i++;

                }

            }

            lastPos = ii.position;

        }


        // Add any new pages we need at the end.

        lastPos = mItems.size() > 0 ? mItems.get(mItems.size() - 1).position

                : -1;

        if (lastPos < endPos) {

            lastPos++;

            lastPos = lastPos > startPos ? lastPos : startPos;

            while (lastPos <= endPos) {

                if (DEBUG)

                    Log.i(TAG, "appending: " + lastPos);

                addNewItem(lastPos, -1);

                lastPos++;

            }

        }


        if (DEBUG) {

            Log.i(TAG, "Current page list:");

            for (int i = 0; i < mItems.size(); i++) {

                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);

            }

        }


        ItemInfo curItem = null;

        for (int i = 0; i < mItems.size(); i++) {

            if (mItems.get(i).position == mCurItem) {

                curItem = mItems.get(i);

                break;

            }

        }

        mAdapter.setPrimaryItem(this, mCurItem,

                curItem != null ? curItem.object : null);


        mAdapter.finishUpdate(this);


        if (hasFocus()) {

            View currentFocused = findFocus();

            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused)

                    : null;

            if (ii == null || ii.position != mCurItem) {

                for (int i = 0; i < getChildCount(); i++) {

                    View child = getChildAt(i);

                    ii = infoForChild(child);

                    if (ii != null && ii.position == mCurItem) {

                        if (child.requestFocus(FOCUS_FORWARD)) {

                            break;

                        }

                    }

                }

            }

        }

    }


    public static class SavedState extends BaseSavedState {

        int position;

        Parcelable adapterState;

        ClassLoader loader;


        public SavedState(Parcelable superState) {

            super(superState);

        }


        @Override

        public void writeToParcel(Parcel out, int flags) {

            super.writeToParcel(out, flags);

            out.writeInt(position);

            out.writeParcelable(adapterState, flags);

        }


        @Override

        public String toString() {

            return "FragmentPager.SavedState{"

                    + Integer.toHexString(System.identityHashCode(this))

                    + " position=" + position + "}";

        }


        public static final Creator<SavedState> CREATOR = ParcelableCompat

                .newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {

                    @Override

                    public SavedState createFromParcel(Parcel in,

                                                       ClassLoader loader) {

                        return new SavedState(in, loader);

                    }


                    @Override

                    public SavedState[] newArray(int size) {

                        return new SavedState[size];

                    }

                });


        SavedState(Parcel in, ClassLoader loader) {

            super(in);

            if (loader == null) {

                loader = getClass().getClassLoader();

            }

            position = in.readInt();

            adapterState = in.readParcelable(loader);

            this.loader = loader;

        }

    }


    @Override

    public Parcelable onSaveInstanceState() {

        Parcelable superState = super.onSaveInstanceState();

        SavedState ss = new SavedState(superState);

        ss.position = mCurItem;

        if (mAdapter != null) {

            ss.adapterState = mAdapter.saveState();

        }

        return ss;

    }


    @Override

    public void onRestoreInstanceState(Parcelable state) {

        if (!(state instanceof SavedState)) {

            super.onRestoreInstanceState(state);

            return;

        }


        SavedState ss = (SavedState) state;

        super.onRestoreInstanceState(ss.getSuperState());


        if (mAdapter != null) {

            mAdapter.restoreState(ss.adapterState, ss.loader);

            setCurrentItemInternal(ss.position, false, true);

        } else {

            mRestoredCurItem = ss.position;

            mRestoredAdapterState = ss.adapterState;

            mRestoredClassLoader = ss.loader;

        }

    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (mInLayout) {
            addViewInLayout(child, index, params);
            child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
        } else {
            super.addView(child, index, params);
        }
        if (USE_CACHE) {
            if (child.getVisibility() != GONE) {
                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
            } else {
                child.setDrawingCacheEnabled(false);
            }
        }
    }

    ItemInfo infoForChild(View child) {
        for (int i = 0; i < mItems.size(); i++) {
            ItemInfo ii = mItems.get(i);
            if (mAdapter.isViewFromObject(child, ii.object)) {
                return ii;
            }
        }
        return null;
    }

    ItemInfo infoForAnyChild(View child) {
        ViewParent parent;
        while ((parent = child.getParent()) != this) {
            if (parent == null || !(parent instanceof View)) {
                return null;
            }
            child = (View) parent;
        }
        return infoForChild(child);

    }


    @Override

    protected void onAttachedToWindow() {

        super.onAttachedToWindow();

        mFirstLayout = true;

    }


    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        // For simple implementation, or internal size is always 0.

        // We depend on the container to specify the layout size of

        // our view. We can't really know what it is since we will be

        // adding and removing different arbitrary views and do not

        // want the layout to change as this happens.

        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),

                getDefaultSize(0, heightMeasureSpec));


        // Children are just made to fill our space.

        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth()

                - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);

        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(

                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),

                MeasureSpec.EXACTLY);


        // Make sure we have created all fragments that we need to have shown.

        mInLayout = true;

        populate();

        mInLayout = false;


        // Make sure all children have been properly measured.

        final int size = getChildCount();

        for (int i = 0; i < size; ++i) {

            final View child = getChildAt(i);

            if (child.getVisibility() != GONE) {

                if (DEBUG)

                    Log.v(TAG, "Measuring #" + i + " " + child + ": "

                            + mChildWidthMeasureSpec);

                child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);

            }

        }

    }


    @Override

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        super.onSizeChanged(w, h, oldw, oldh);


        // Make sure scroll position is set correctly.

        if (w != oldw) {

            recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);

        }

    }


    private void recomputeScrollPosition(int width, int oldWidth, int margin,

                                         int oldMargin) {

        final int widthWithMargin = width + margin;

        if (oldWidth > 0) {

            final int oldScrollPos = getScrollX();

            final int oldwwm = oldWidth + oldMargin;

            final int oldScrollItem = oldScrollPos / oldwwm;

            final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;

            final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);

            scrollTo(scrollPos, getScrollY());

            if (!mScroller.isFinished()) {

                // We now return to your regularly scheduled scroll, already in

                // progress.

                final int newDuration = mScroller.getDuration()

                        - mScroller.timePassed();

                mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin,

                        0, newDuration);

            }

        } else {

            int scrollPos = mCurItem * widthWithMargin;

            if (scrollPos != getScrollX()) {

                completeScroll();

                scrollTo(scrollPos, getScrollY());

            }

        }

    }


    @Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        mInLayout = true;

        populate();

        mInLayout = false;


        final int count = getChildCount();

        final int width = r - l;


        for (int i = 0; i < count; i++) {

            View child = getChildAt(i);

            ItemInfo ii;

            if (child.getVisibility() != GONE

                    && (ii = infoForChild(child)) != null) {

                int loff = (width + mPageMargin) * ii.position;

                int childLeft = getPaddingLeft() + loff;

                int childTop = getPaddingTop();

                if (DEBUG)

                    Log.v(TAG,

                            "Positioning #" + i + " " + child + " f="

                                    + ii.object + ":" + childLeft + ","

                                    + childTop + " " + child.getMeasuredWidth()

                                    + "x" + child.getMeasuredHeight());

                child.layout(childLeft, childTop,

                        childLeft + child.getMeasuredWidth(),

                        childTop + child.getMeasuredHeight());

            }

        }

        mFirstLayout = false;

    }


    @Override

    public void computeScroll() {

        if (DEBUG)

            Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());

        if (!mScroller.isFinished()) {

            if (mScroller.computeScrollOffset()) {

                if (DEBUG)

                    Log.i(TAG, "computeScroll: still scrolling");

                int oldX = getScrollX();

                int oldY = getScrollY();

                int x = mScroller.getCurrX();

                int y = mScroller.getCurrY();


                if (oldX != x || oldY != y) {

                    scrollTo(x, y);

                }


                if (mOnPageChangeListener != null) {

                    final int widthWithMargin = getWidth() + mPageMargin;

                    final int position = x / widthWithMargin;

                    final int offsetPixels = x % widthWithMargin;

                    final float offset = (float) offsetPixels / widthWithMargin;

                    mOnPageChangeListener.onPageScrolled(position, offset,

                            offsetPixels);

                }


                // Keep on drawing until the animation has finished.

                invalidate();

                return;

            }

        }


        // Done with scroll, clean up state.

        completeScroll();

    }


    private void completeScroll() {

        boolean needPopulate = mScrolling;

        if (needPopulate) {

            // Done with scroll, no longer want to cache view drawing.

            setScrollingCacheEnabled(false);

            mScroller.abortAnimation();

            int oldX = getScrollX();

            int oldY = getScrollY();

            int x = mScroller.getCurrX();

            int y = mScroller.getCurrY();

            if (oldX != x || oldY != y) {

                scrollTo(x, y);

            }

            setScrollState(SCROLL_STATE_IDLE);

        }

        mPopulatePending = false;

        mScrolling = false;

        for (int i = 0; i < mItems.size(); i++) {

            ItemInfo ii = mItems.get(i);

            if (ii.scrolling) {

                needPopulate = true;

                ii.scrolling = false;

            }

        }

        if (needPopulate) {

            populate();

        }

    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */
        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
        // Always take care of the touch gesture being complete.
        if (action == MotionEvent.ACTION_CANCEL
                || action == MotionEvent.ACTION_UP) {
            // Release the drag.
            if (DEBUG)
                Log.v(TAG, "Intercept done!");
            mIsBeingDragged = false;
            mIsUnableToDrag = false;
            mActivePointerId = INVALID_POINTER;
            return false;

        }


        // Nothing more to do here if we have decided whether or not we

        // are dragging.

        if (action != MotionEvent.ACTION_DOWN) {

            if (mIsBeingDragged) {

                if (DEBUG)

                    Log.v(TAG, "Intercept returning true!");

                return true;

            }

            if (mIsUnableToDrag) {

                if (DEBUG)

                    Log.v(TAG, "Intercept returning false!");

                return false;

            }

        }


        switch (action) {

            case MotionEvent.ACTION_MOVE: {

                /*

                 * mIsBeingDragged == false, otherwise the shortcut would have

                 * caught it. Check whether the user has moved far enough from his

                 * original down touch.

                 */



                /*

                 * Locally do absolute value. mLastMotionY is set to the y value of

                 * the down event.

                 */

                final int activePointerId = mActivePointerId;

                if (activePointerId == INVALID_POINTER) {

                    // If we don't have a valid id, the touch down wasn't on

                    // content.

                    break;

                }


                final int pointerIndex = MotionEventCompat.findPointerIndex(ev,

                        activePointerId);

                final float x = MotionEventCompat.getX(ev, pointerIndex);

                final float dx = x - mLastMotionX;

                final float xDiff = Math.abs(dx);

                final float y = MotionEventCompat.getY(ev, pointerIndex);

                final float yDiff = Math.abs(y - mLastMotionY);

                final int scrollX = getScrollX();

                final boolean atEdge = (dx > 0 && scrollX == 0)

                        || (dx < 0 && mAdapter != null && scrollX >= (mAdapter

                        .getCount() - 1) * getWidth() - 1);

                if (DEBUG)

                    Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + ","

                            + yDiff);


                if (canScroll(this, false, (int) dx, (int) x, (int) y)) {

                    // Nested view has scrollable area under this point. Let it be

                    // handled there.

                    mInitialMotionX = mLastMotionX = x;

                    mLastMotionY = y;

                    return false;

                }

                if (xDiff > mTouchSlop && xDiff > yDiff) {

                    if (DEBUG)

                        Log.v(TAG, "Starting drag!");

                    mIsBeingDragged = true;

                    setScrollState(SCROLL_STATE_DRAGGING);

                    mLastMotionX = x;

                    setScrollingCacheEnabled(true);

                } else {

                    if (yDiff > mTouchSlop) {

                        // The finger has moved enough in the vertical

                        // direction to be counted as a drag... abort

                        // any attempt to drag horizontally, to work correctly

                        // with children that have scrolling containers.

                        if (DEBUG)

                            Log.v(TAG, "Starting unable to drag!");

                        mIsUnableToDrag = true;

                    }

                }

                break;

            }


            case MotionEvent.ACTION_DOWN: {

                /*

                 * Remember location of down touch. ACTION_DOWN always refers to

                 * pointer index 0.

                 */

                mLastMotionX = mInitialMotionX = ev.getX();

                mLastMotionY = ev.getY();

                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);


                if (mScrollState == SCROLL_STATE_SETTLING) {

                    // Let the user 'catch' the pager as it animates.

                    mIsBeingDragged = true;

                    mIsUnableToDrag = false;

                    setScrollState(SCROLL_STATE_DRAGGING);

                } else {

                    completeScroll();

                    mIsBeingDragged = false;

                    mIsUnableToDrag = false;

                }


                if (DEBUG)

                    Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY

                            + " mIsBeingDragged=" + mIsBeingDragged

                            + "mIsUnableToDrag=" + mIsUnableToDrag);

                break;

            }


            case MotionEventCompat.ACTION_POINTER_UP:

                onSecondaryPointerUp(ev);

                break;

        }



        /*

         * The only time we want to intercept motion events is if we are in the

         * drag mode.

         */

        return mIsBeingDragged;

    }


    @Override

    public boolean onTouchEvent(MotionEvent ev) {

        if (mFakeDragging) {

            // A fake drag is in progress already, ignore this real one

            // but still eat the touch events.

            // (It is likely that the user is multi-touching the screen.)

            return true;

        }


        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {

            // Don't handle edge touches immediately -- they may actually belong

            // to one of our

            // descendants.

            return false;

        }


        if (mAdapter == null || mAdapter.getCount() == 0) {

            // Nothing to present or scroll; nothing to touch.

            return false;

        }


        if (mVelocityTracker == null) {

            mVelocityTracker = VelocityTracker.obtain();

        }

        mVelocityTracker.addMovement(ev);


        final int action = ev.getAction();

        boolean needsInvalidate = false;


        switch (action & MotionEventCompat.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN: {

                /*

                 * If being flinged and user touches, stop the fling. isFinished

                 * will be false if being flinged.

                 */

                completeScroll();


                // Remember where the motion event started

                mLastMotionX = mInitialMotionX = ev.getX();

                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);

                break;

            }

            case MotionEvent.ACTION_MOVE:

                if (!mIsBeingDragged) {

                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev,

                            mActivePointerId);

                    final float x = MotionEventCompat.getX(ev, pointerIndex);

                    final float xDiff = Math.abs(x - mLastMotionX);

                    final float y = MotionEventCompat.getY(ev, pointerIndex);

                    final float yDiff = Math.abs(y - mLastMotionY);

                    if (DEBUG)

                        Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff

                                + "," + yDiff);

                    if (xDiff > mTouchSlop && xDiff > yDiff) {

                        if (DEBUG)

                            Log.v(TAG, "Starting drag!");

                        mIsBeingDragged = true;

                        mLastMotionX = x;

                        setScrollState(SCROLL_STATE_DRAGGING);

                        setScrollingCacheEnabled(true);

                    }

                }

                if (mIsBeingDragged) {

                    // Scroll to follow the motion event

                    final int activePointerIndex = MotionEventCompat

                            .findPointerIndex(ev, mActivePointerId);

                    final float x = MotionEventCompat.getX(ev, activePointerIndex);

                    final float deltaX = mLastMotionX - x;

                    mLastMotionX = x;

                    float oldScrollX = getScrollX();

                    float scrollX = oldScrollX + deltaX;

                    final int width = getWidth();

                    final int widthWithMargin = width + mPageMargin;


                    final int lastItemIndex = mAdapter.getCount() - 1;

                    final float leftBound = Math.max(0, (mCurItem - 1)

                            * widthWithMargin);

                    final float rightBound = Math.min(mCurItem + 1, lastItemIndex)

                            * widthWithMargin;

                    if (scrollX < leftBound) {

                        if (leftBound == 0) {

                            float over = -scrollX;

                            needsInvalidate = mLeftEdge.onPull(over / width);

                        }

                        scrollX = leftBound;

                    } else if (scrollX > rightBound) {

                        if (rightBound == lastItemIndex * widthWithMargin) {

                            float over = scrollX - rightBound;

                            needsInvalidate = mRightEdge.onPull(over / width);

                        }

                        scrollX = rightBound;

                    }

                    // Don't lose the rounded component

                    mLastMotionX += scrollX - (int) scrollX;

                    scrollTo((int) scrollX, getScrollY());

                    if (mOnPageChangeListener != null) {

                        final int position = (int) scrollX / widthWithMargin;

                        final int positionOffsetPixels = (int) scrollX

                                % widthWithMargin;

                        final float positionOffset = (float) positionOffsetPixels

                                / widthWithMargin;

                        mOnPageChangeListener.onPageScrolled(position,

                                positionOffset, positionOffsetPixels);

                    }

                }

                break;

            case MotionEvent.ACTION_UP:

                if (mIsBeingDragged) {

                    final VelocityTracker velocityTracker = mVelocityTracker;

                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(

                            velocityTracker, mActivePointerId);

                    mPopulatePending = true;

                    final int widthWithMargin = getWidth() + mPageMargin;

                    final int scrollX = getScrollX();

                    final int currentPage = scrollX / widthWithMargin;

                    int nextPage = initialVelocity > 0 ? currentPage

                            : currentPage + 1;

                    setCurrentItemInternal(nextPage, true, true, initialVelocity);


                    mActivePointerId = INVALID_POINTER;

                    endDrag();

                    needsInvalidate = mLeftEdge.onRelease()

                            | mRightEdge.onRelease();

                }

                break;

            case MotionEvent.ACTION_CANCEL:

                if (mIsBeingDragged) {

                    setCurrentItemInternal(mCurItem, true, true);

                    mActivePointerId = INVALID_POINTER;

                    endDrag();

                    needsInvalidate = mLeftEdge.onRelease()

                            | mRightEdge.onRelease();

                }

                break;

            case MotionEventCompat.ACTION_POINTER_DOWN: {

                final int index = MotionEventCompat.getActionIndex(ev);

                final float x = MotionEventCompat.getX(ev, index);

                mLastMotionX = x;

                mActivePointerId = MotionEventCompat.getPointerId(ev, index);

                break;

            }

            case MotionEventCompat.ACTION_POINTER_UP:

                onSecondaryPointerUp(ev);

                mLastMotionX = MotionEventCompat.getX(ev,

                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));

                break;

        }

        if (needsInvalidate) {

            invalidate();

        }

        return true;

    }


    @Override

    public void draw(Canvas canvas) {

        super.draw(canvas);

        boolean needsInvalidate = false;


        final int overScrollMode = ViewCompat.getOverScrollMode(this);

        if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS

                || (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS

                && mAdapter != null && mAdapter.getCount() > 1)) {

            if (!mLeftEdge.isFinished()) {

                final int restoreCount = canvas.save();

                final int height = getHeight() - getPaddingTop()

                        - getPaddingBottom();


                canvas.rotate(270);

                canvas.translate(-height + getPaddingTop(), 0);

                mLeftEdge.setSize(height, getWidth());

                needsInvalidate |= mLeftEdge.draw(canvas);

                canvas.restoreToCount(restoreCount);

            }

            if (!mRightEdge.isFinished()) {

                final int restoreCount = canvas.save();

                final int width = getWidth();

                final int height = getHeight() - getPaddingTop()

                        - getPaddingBottom();

                final int itemCount = mAdapter != null ? mAdapter.getCount()

                        : 1;


                canvas.rotate(90);

                canvas.translate(-getPaddingTop(), -itemCount

                        * (width + mPageMargin) + mPageMargin);

                mRightEdge.setSize(height, width);

                needsInvalidate |= mRightEdge.draw(canvas);

                canvas.restoreToCount(restoreCount);

            }

        } else {

            mLeftEdge.finish();

            mRightEdge.finish();

        }


        if (needsInvalidate) {

            // Keep animating

            invalidate();

        }

    }


    @Override

    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);


        // Draw the margin drawable if needed.

        if (mPageMargin > 0 && mMarginDrawable != null) {

            final int scrollX = getScrollX();

            final int width = getWidth();

            final int offset = scrollX % (width + mPageMargin);

            if (offset != 0) {

                // Pages fit completely when settled; we only need to draw when

                // in between

                final int left = scrollX - offset + width;

                mMarginDrawable.setBounds(left, 0, left + mPageMargin,

                        getHeight());

                mMarginDrawable.draw(canvas);

            }

        }

    }


    /**
     * Start a fake drag of the pager.
     * <p>
     * <p>
     * <p>
     * <p>
     * <p>
     * A fake drag can be useful if you want to synchronize the motion of the
     * <p>
     * ViewPager with the touch scrolling of another view, while still letting
     * <p>
     * the ViewPager control the snapping motion and fling behavior. (e.g.
     * <p>
     * parallax-scrolling tabs.) Call {@link #fakeDragBy(float)} to simulate the
     * <p>
     * actual drag motion. Call {@link #endFakeDrag()} to complete the fake drag
     * <p>
     * and fling as necessary.
     * <p>
     * <p>
     * <p>
     * <p>
     * <p>
     * During a fake drag the ViewPager will ignore all touch events. If a real
     * <p>
     * drag is already in progress, this method will return false.
     *
     * @return true if the fake drag began successfully, false if it could not
     * <p>
     * be started.
     * @see #fakeDragBy(float)
     * @see #endFakeDrag()
     */

    public boolean beginFakeDrag() {

        if (mIsBeingDragged) {

            return false;

        }

        mFakeDragging = true;

        setScrollState(SCROLL_STATE_DRAGGING);

        mInitialMotionX = mLastMotionX = 0;

        if (mVelocityTracker == null) {

            mVelocityTracker = VelocityTracker.obtain();

        } else {

            mVelocityTracker.clear();

        }

        final long time = SystemClock.uptimeMillis();

        final MotionEvent ev = MotionEvent.obtain(time, time,

                MotionEvent.ACTION_DOWN, 0, 0, 0);

        mVelocityTracker.addMovement(ev);

        ev.recycle();

        mFakeDragBeginTime = time;

        return true;

    }


    /**
     * End a fake drag of the pager.
     *
     * @see #beginFakeDrag()
     * @see #fakeDragBy(float)
     */

    public void endFakeDrag() {

        if (!mFakeDragging) {

            throw new IllegalStateException(

                    "No fake drag in progress. Call beginFakeDrag first.");

        }


        final VelocityTracker velocityTracker = mVelocityTracker;

        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

        int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(

                velocityTracker, mActivePointerId);

        mPopulatePending = true;

        if ((Math.abs(initialVelocity) > mMinimumVelocity)

                || Math.abs(mInitialMotionX - mLastMotionX) >= (getWidth() / 3)) {

            if (mLastMotionX > mInitialMotionX) {

                setCurrentItemInternal(mCurItem - 1, true, true);

            } else {

                setCurrentItemInternal(mCurItem + 1, true, true);

            }

        } else {

            setCurrentItemInternal(mCurItem, true, true);

        }

        endDrag();


        mFakeDragging = false;

    }


    /**
     * Fake drag by an offset in pixels. You must have called
     * <p>
     * {@link #beginFakeDrag()} first.
     *
     * @param xOffset Offset in pixels to drag by.
     * @see #beginFakeDrag()
     * @see #endFakeDrag()
     */

    public void fakeDragBy(float xOffset) {

        if (!mFakeDragging) {

            throw new IllegalStateException(

                    "No fake drag in progress. Call beginFakeDrag first.");

        }


        mLastMotionX += xOffset;

        float scrollX = getScrollX() - xOffset;

        final int width = getWidth();

        final int widthWithMargin = width + mPageMargin;


        final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);

        final float rightBound = Math

                .min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;

        if (scrollX < leftBound) {

            scrollX = leftBound;

        } else if (scrollX > rightBound) {

            scrollX = rightBound;

        }

        // Don't lose the rounded component

        mLastMotionX += scrollX - (int) scrollX;

        scrollTo((int) scrollX, getScrollY());

        if (mOnPageChangeListener != null) {

            final int position = (int) scrollX / widthWithMargin;

            final int positionOffsetPixels = (int) scrollX % widthWithMargin;

            final float positionOffset = (float) positionOffsetPixels

                    / widthWithMargin;

            mOnPageChangeListener.onPageScrolled(position, positionOffset,

                    positionOffsetPixels);

        }


        // Synthesize an event for the VelocityTracker.

        final long time = SystemClock.uptimeMillis();

        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time,

                MotionEvent.ACTION_MOVE, mLastMotionX, 0, 0);

        mVelocityTracker.addMovement(ev);

        ev.recycle();

    }


    /**
     * Returns true if a fake drag is in progress.
     *
     * @return true if currently in a fake drag, false otherwise.
     * @see #beginFakeDrag()
     * @see #fakeDragBy(float)
     * @see #endFakeDrag()
     */

    public boolean isFakeDragging() {

        return mFakeDragging;

    }


    private void onSecondaryPointerUp(MotionEvent ev) {

        final int pointerIndex = MotionEventCompat.getActionIndex(ev);

        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);

        if (pointerId == mActivePointerId) {

            // This was our active pointer going up. Choose a new

            // active pointer and adjust accordingly.

            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;

            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);

            mActivePointerId = MotionEventCompat.getPointerId(ev,

                    newPointerIndex);

            if (mVelocityTracker != null) {

                mVelocityTracker.clear();

            }

        }

    }


    private void endDrag() {

        mIsBeingDragged = false;

        mIsUnableToDrag = false;


        if (mVelocityTracker != null) {

            mVelocityTracker.recycle();

            mVelocityTracker = null;

        }

    }


    private void setScrollingCacheEnabled(boolean enabled) {

        if (mScrollingCacheEnabled != enabled) {

            mScrollingCacheEnabled = enabled;

            if (USE_CACHE) {

                final int size = getChildCount();

                for (int i = 0; i < size; ++i) {

                    final View child = getChildAt(i);

                    if (child.getVisibility() != GONE) {

                        child.setDrawingCacheEnabled(enabled);

                    }

                }

            }

        }

    }


    /**
     * Tests scrollability within child views of v given a delta of dx.
     *
     * @param v      View to test for horizontal scrollability
     * @param checkV Whether the view v passed should itself be checked for
     *               <p>
     *               scrollability (true), or just its children (false).
     * @param dx     Delta scrolled in pixels
     * @param x      X coordinate of the active touch point
     * @param y      Y coordinate of the active touch point
     * @return true if child views of v can be scrolled by delta of dx.
     */

    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {

        if (v instanceof ViewGroup) {

            final ViewGroup group = (ViewGroup) v;

            final int scrollX = v.getScrollX();

            final int scrollY = v.getScrollY();

            final int count = group.getChildCount();

            // Count backwards - let topmost views consume scroll distance

            // first.

            for (int i = count - 1; i >= 0; i--) {

                // TODO: Add versioned support here for transformed views.

                // This will not work for transformed views in Honeycomb+

                final View child = group.getChildAt(i);

                if (x + scrollX >= child.getLeft()

                        && x + scrollX < child.getRight()

                        && y + scrollY >= child.getTop()

                        && y + scrollY < child.getBottom()

                        && canScroll(child, true, dx,

                        x + scrollX - child.getLeft(), y + scrollY

                                - child.getTop())) {

                    return true;

                }

            }

        }


        return checkV && ViewCompat.canScrollHorizontally(v, -dx);

    }


    @Override

    public boolean dispatchKeyEvent(KeyEvent event) {

        // Let the focused view and/or our descendants get the key first

        return super.dispatchKeyEvent(event) || executeKeyEvent(event);

    }


    /**
     * You can call this function yourself to have the scroll view perform
     * <p>
     * scrolling from a key event, just as if the event had been dispatched to
     * <p>
     * it by the view hierarchy.
     *
     * @param event The key event to execute.
     * @return Return true if the event was handled, else false.
     */

    public boolean executeKeyEvent(KeyEvent event) {

        boolean handled = false;

        if (event.getAction() == KeyEvent.ACTION_DOWN) {

            switch (event.getKeyCode()) {

                case KeyEvent.KEYCODE_DPAD_LEFT:

                    handled = arrowScroll(FOCUS_LEFT);

                    break;

                case KeyEvent.KEYCODE_DPAD_RIGHT:

                    handled = arrowScroll(FOCUS_RIGHT);

                    break;

                case KeyEvent.KEYCODE_TAB:

                    if (KeyEventCompat.hasNoModifiers(event)) {

                        handled = arrowScroll(FOCUS_FORWARD);

                    } else if (KeyEventCompat.hasModifiers(event,

                            KeyEvent.META_SHIFT_ON)) {

                        handled = arrowScroll(FOCUS_BACKWARD);

                    }

                    break;

            }

        }

        return handled;

    }


    public boolean arrowScroll(int direction) {

        View currentFocused = findFocus();

        if (currentFocused == this)

            currentFocused = null;


        boolean handled = false;


        View nextFocused = FocusFinder.getInstance().findNextFocus(this,

                currentFocused, direction);

        if (nextFocused != null && nextFocused != currentFocused) {

            if (direction == View.FOCUS_LEFT) {

                // If there is nothing to the left, or this is causing us to

                // jump to the right, then what we really want to do is page

                // left.

                if (currentFocused != null

                        && nextFocused.getLeft() >= currentFocused.getLeft()) {

                    handled = pageLeft();

                } else {

                    handled = nextFocused.requestFocus();

                }

            } else if (direction == View.FOCUS_RIGHT) {

                // If there is nothing to the right, or this is causing us to

                // jump to the left, then what we really want to do is page

                // right.

                if (currentFocused != null

                        && nextFocused.getLeft() <= currentFocused.getLeft()) {

                    handled = pageRight();

                } else {

                    handled = nextFocused.requestFocus();

                }

            }

        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {

            // Trying to move left and nothing there; try to page.

            handled = pageLeft();

        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {

            // Trying to move right and nothing there; try to page.

            handled = pageRight();

        }

        if (handled) {

            playSoundEffect(SoundEffectConstants

                    .getContantForFocusDirection(direction));

        }

        return handled;

    }


    boolean pageLeft() {

        if (mCurItem > 0) {

            setCurrentItem(mCurItem - 1, true);

            return true;

        }

        return false;

    }


    boolean pageRight() {

        if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {

            setCurrentItem(mCurItem + 1, true);

            return true;

        }

        return false;

    }


    /**
     * We only want the current page that is being shown to be focusable.
     */

    @Override

    public void addFocusables(ArrayList<View> views, int direction,

                              int focusableMode) {

        final int focusableCount = views.size();


        final int descendantFocusability = getDescendantFocusability();


        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {

            for (int i = 0; i < getChildCount(); i++) {

                final View child = getChildAt(i);

                if (child.getVisibility() == VISIBLE) {

                    ItemInfo ii = infoForChild(child);

                    if (ii != null && ii.position == mCurItem) {

                        child.addFocusables(views, direction, focusableMode);

                    }

                }

            }

        }


        // we add ourselves (if focusable) in all cases except for when we are

        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.

        // this is

        // to avoid the focus search finding layouts when a more precise search

        // among the focusable children would be more interesting.

        if (descendantFocusability != FOCUS_AFTER_DESCENDANTS ||

                // No focusable descendants

                (focusableCount == views.size())) {

            // Note that we can't call the superclass here, because it will

            // add all views in. So we need to do the same thing View does.

            if (!isFocusable()) {

                return;

            }

            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE

                    && isInTouchMode() && !isFocusableInTouchMode()) {

                return;

            }

            if (views != null) {

                views.add(this);

            }

        }

    }


    /**
     * We only want the current page that is being shown to be touchable.
     */

    @Override

    public void addTouchables(ArrayList<View> views) {

        // Note that we don't call super.addTouchables(), which means that

        // we don't call View.addTouchables(). This is okay because a ViewPager

        // is itself not touchable.

        for (int i = 0; i < getChildCount(); i++) {

            final View child = getChildAt(i);

            if (child.getVisibility() == VISIBLE) {

                ItemInfo ii = infoForChild(child);

                if (ii != null && ii.position == mCurItem) {

                    child.addTouchables(views);

                }

            }

        }

    }


    /**
     * We only want the current page that is being shown to be focusable.
     */

    @Override

    protected boolean onRequestFocusInDescendants(int direction,

                                                  Rect previouslyFocusedRect) {

        int index;

        int increment;

        int end;

        int count = getChildCount();
        if ((direction & FOCUS_FORWARD) != 0) {
            index = 0;
            increment = 1;
            end = count;
        } else {
            index = count - 1;
            increment = -1;
            end = -1;
        }
        for (int i = index; i != end; i += increment) {
            View child = getChildAt(i);
            if (child.getVisibility() == VISIBLE) {
                ItemInfo ii = infoForChild(child);
                if (ii != null && ii.position == mCurItem) {
                    if (child.requestFocus(direction, previouslyFocusedRect)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
        // ViewPagers should only report accessibility info for the current
        // page,
        // otherwise things get very confusing.
        // TODO: Should this note something about the paging container?
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == VISIBLE) {
                final ItemInfo ii = infoForChild(child);
                if (ii != null && ii.position == mCurItem
                        && child.dispatchPopulateAccessibilityEvent(event)) {
                    return true;
                }
            }
        }
        return false;
    }

    private class PagerObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            dataSetChanged();
        }

        @Override
        public void onInvalidated() {
            dataSetChanged();
        }
    }
}
